package org.phalanx.client.UI.swing;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.imageio.ImageIO;
import org.phalanx.map.MapCoordinates;
import org.phalanx.map.MapTile;
import org.phalanx.map.MapTileSet;
import org.phalanx.map.PhalanxMap;
import org.phalanx.zip.ZipFileArchiveReader;

public class PhalanxMapView extends Canvas {
	private PhalanxMap contextMap;
	private Integer currentX = 0;
	private Integer currentY = 0;
	private boolean drawGrid = true;
	private Color gridColor = Color.black;
	private Color bgColor = Color.white;
	private Double zoomLevel;
	BufferedImage newTile;
	private Hashtable<String,BufferedImage> cachedImageTable;
	private Hashtable<String,Image> zoomImageTable;
	protected BufferedImage graphicsBuffer;
	private BufferedImage lastImage;
	
	public static final Double ZOOM_DEFAULT = 1.0;
	public static final Double ZOOMIN_LEVEL_1 = 1.5;
	public static final Double ZOOMIN_LEVEL_2 = 2.0;
	public static final Double ZOOMOUT_LEVEL_1 = 0.75;
	public static final Double ZOOMOUT_LEVEL_2 = 0.5;
	public static final Double ZOOMOUT_LEVEL_3 = 0.25;
	
	
	public PhalanxMapView(PhalanxMap newMap){
		 this.contextMap = newMap;
		 setInitialDefaults();
		 cacheImages();
		 rehashImagesForZoom();
	}
	
	private void setInitialDefaults(){
		this.setBackground(bgColor);
		this.setForeground(Color.black);
		this.setMinimumSize(new Dimension(300,200));
		this.setZoomLevel(ZOOM_DEFAULT);
	}
	
	public void setMapTileSet(MapTileSet newSet) {
		this.contextMap.setMapTileSet(newSet);
		this.cacheImages();
		rehashImagesForZoom();
	}
	
	private void cacheImages(){
		cachedImageTable = new Hashtable<String,BufferedImage>();
		zoomImageTable = new Hashtable<String,Image>();
		MapTileSet m = contextMap.getMapTileSet();
		Enumeration<String> e = m.getMapTileSetTable().keys();

		
		while (e.hasMoreElements()){
			String ref = e.nextElement();
			String filePath = m.getMapTileByName(ref).getImageReference();
			try {
				ZipFileArchiveReader z = new ZipFileArchiveReader(m.getTileSetArchiveFile());
				byte[] b = z.getArchivedFile(filePath);

				BufferedImage aTile = ImageIO.read(new ByteArrayInputStream(b));
				cachedImageTable.put(filePath, aTile);
			} catch (IOException ie){ie.printStackTrace();}
		}
		
	}

	private void rehashImagesForZoom(){
		if (cachedImageTable != null){
			Enumeration<String> e = cachedImageTable.keys();

			while(e.hasMoreElements()){
				String filePath = e.nextElement();

				BufferedImage b = cachedImageTable.get(filePath);
				Image scaledImage = b.getScaledInstance(this.getTileSize(), this.getTileSize(), Image.SCALE_FAST);

				zoomImageTable.put(filePath, scaledImage);
			}
		}
	}
	
	private Image getImage (String filePath){
		Image b = zoomImageTable.get(filePath);
		
		return b;
	}
	
	public boolean getIsGridDrawn(){
		return drawGrid;
	}
	
	public void setIsGridDrawn(boolean draw){
		this.drawGrid = draw;
		this.repaint();
	}
	
	public PhalanxMap getMap(){
		return contextMap;
	}
	
	public void setMap(PhalanxMap newMap){
		contextMap = newMap;
		 cacheImages();
		 rehashImagesForZoom();
	}
	
	public Integer getXPos(){
		return currentX;
	}
	
	public Integer getYPos(){
		return currentY;
	}
	
	public void setCoordinates(Integer newX, Integer newY){
		Integer mapLength = contextMap.getMapLength();
		Integer mapWidth = contextMap.getMapWidth();
		
		if (!(newX > mapWidth - 1 || newY > mapLength - 1 || newX < 0 || newY < 0)){
			currentX = newX;
			currentY = newY;
		}
	}
	
	public void setZoomLevel(Double zoom){
		if (zoom <= ZOOMIN_LEVEL_2 && zoom >= ZOOMOUT_LEVEL_3){
			this.zoomLevel = zoom;
			this.rehashImagesForZoom();
		}
	}
	
	public Double getZoomLevel(){
		return this.zoomLevel;
	}
	
	public Integer getTileSize(){
		return new Double(Math.floor(getActualTileSize() * this.getZoomLevel())).intValue();
	}
	
	public Integer getActualTileSize(){
		return contextMap.getTileSize();
	}
	
	public void renderMap(){
		// Very important function that takes map data and draws it on the screen
		// as well as the grid if enabled
		
		Integer getTileWidthCount = getMapTileX();
		Integer getTileLengthCount = getMapTileY();
		Integer getCenterPosW = getCenterPosTile(getTileWidthCount);
		Integer getCenterPosL = getCenterPosTile(getTileLengthCount);
		
		graphicsBuffer = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
		((Graphics2D)graphicsBuffer.getGraphics()).setBackground(Color.black);
		((Graphics2D)graphicsBuffer.getGraphics()).setPaint(Color.black);
		
		preMapDrawing();
		
		if ((this.getXPos() - getCenterPosW) <= 0){
			this.setCoordinates(getCenterPosW, this.getYPos());
		}
		if (this.getYPos() - getCenterPosL <= 0){
			this.setCoordinates(this.getXPos(), getCenterPosL);
		}
		int startPointY = currentY - getCenterPosL;
		int startPointX = currentX - getCenterPosW;
		
		int endPointY = startPointY + getTileLengthCount > this.getMap().getMapLength() ?
				this.getMap().getMapLength() : startPointY + getTileLengthCount;
		int endPointX = startPointX + getTileWidthCount > this.getMap().getMapWidth() ?
				this.getMap().getMapWidth() : startPointX + getTileWidthCount;
		
		for (int i = startPointY; i < endPointY; i++){
			for (int j = startPointX; j < endPointX; j++){
				try {
					drawTile(j ,i);
				} catch (IOException e){}
			}
		}
		
		drawGridLines();
		postMapDrawing();
		transferBufferToConsole();
	}
	
	protected void preMapDrawing(){
		
	}
	
	protected void postMapDrawing(){
		
	}
	
	public Integer getPixelPointx(Integer tileX){
		int virtualX = (getCenterPosTile(getMapTileX())  - currentX) + tileX;
		return new Double(virtualX * getTileSize()).intValue();
	}
	
	public Integer getPixelPointy(Integer tileY){
		int virtualY = (getCenterPosTile(getMapTileY()) - currentY) + tileY;
		return new Double(virtualY * getTileSize()).intValue();
	}
	
	private void drawTile(Integer x, Integer y) throws IOException{
/*		int virtualX = (getCenterPosTile(getMapTileX())  - currentX) + x;
		int virtualY = (getCenterPosTile(getMapTileY()) - currentY) + y;
		Integer pixelPointX = new Double(virtualX * getTileSize()).intValue();
		Integer pixelPointY = new Double(virtualY * getTileSize()).intValue();*/
		
		Integer pixelPointX = getPixelPointx(x);
		Integer pixelPointY = getPixelPointy(y);
		
		Graphics2D g = (Graphics2D)this.getGraphics();
		//g.drawImage(newTile, null, pixelPointX, pixelPointY);
		//g.drawImage(newTile, pixelPointX, pixelPointY, this.getTileSize() * this.getZoomLevel(), this.getTileSize() * this.getZoomLevel(), null);
		MapTile xyTile = this.getMap().getMapTileAt(new MapCoordinates(x, y));
		
		
		if (xyTile != null){
			//g.drawImage(getImage(xyTile.getImageReference()), pixelPointX.intValue(), pixelPointY.intValue(), null);
			Graphics2D gfxBuffer = (Graphics2D)graphicsBuffer.getGraphics();
			gfxBuffer.drawImage(getImage(xyTile.getImageReference()), pixelPointX.intValue(), pixelPointY.intValue(), null);
		}
	}
	
	private void transferBufferToConsole(){
		if (graphicsBuffer != null){
			Graphics2D g = (Graphics2D)this.getGraphics();
			g.drawImage(graphicsBuffer, null, 0, 0);
		}
	}
	
	private void drawGridLines(){
		if (this.drawGrid){
			Graphics2D g = (Graphics2D)graphicsBuffer.getGraphics();
			g.setColor(Color.black);
			
			if (g != null){
				// X coordinates
				for (int i = 0; i < getMapTileX(); i++){
					int xCoord = i * this.getTileSize();
					g.drawLine(xCoord, 0, xCoord, this.getHeight());
				}
				// Y coordinates
				for (int i = 0; i < getMapTileY();i++){
					int yCoord = i * this.getTileSize();
					g.drawLine(0, yCoord, this.getWidth(), yCoord);
				}
			}
		}
	}
	
	private Integer getCenterPosTile(Integer w){
		return w / 2;
	}
	
	public MapCoordinates getMapTilePosition(Integer pX, Integer pY){
		MapCoordinates c = null;
		
		Integer getCenterPosW = getCenterPosTile(this.getMapTileX());
		Integer getCenterPosL = getCenterPosTile(this.getMapTileY());
		
		Integer xCoordinateAt0 = currentX - getCenterPosW;
		Integer yCoordinateAt0 = currentY - getCenterPosL;
		
		Integer virtualPositionX = pX / this.getTileSize();	
		Integer virtualPositionY = pY / this.getTileSize();
		
		c = new MapCoordinates(xCoordinateAt0 + virtualPositionX,yCoordinateAt0 + virtualPositionY);
		
		return c;
	}
	
	private Integer getMapTileX(){
		Integer tileSize = getTileSize();
		Integer componentXSize = this.getWidth();
		
		Integer maxTilesX = roundUp(componentXSize.doubleValue() / tileSize.doubleValue());
		
		return maxTilesX;
	}
	
	private Integer getMapTileY(){
		Integer tileSize = getTileSize();
		Integer componentYSize = this.getHeight();
		
		Integer maxTilesY = roundUp(componentYSize.doubleValue() / tileSize.doubleValue());
		
		return maxTilesY;
	}
	
	private Integer roundUp(Double d){
		return new Double(Math.rint(d + 0.5f)).intValue();
	}

	@Override
	public void paint(Graphics g) {
		//this.transferBufferToConsole();
		this.renderMap();
	}

	@Override
	public void repaint() {
		this.paint(this.getGraphics());
	}
	
}
